/*
 * This file referenced Lionel Brits & Jeff Molofee's openGL Tutorials,The tutorials get from http://nehe.gamedev.net/
 */
#define GL_GLEXT_PROTOTYPES

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>

#define IMG_W 256
#define IMG_H 256

const float piover180 = 0.0174532925f;
float heading;
float xpos;
float zpos;

GLfloat	yrot;				// Y Rotation
GLfloat walkbias = 0;
GLfloat walkbiasangle = 0;
GLfloat lookupdown = 0.0f;

GLuint	filter;				// Which Filter To Use
GLuint	texture[3];			// Storage For 3 Textures

GLboolean blend;

struct VERTEX{
	float x,y,z;
	float u,v;
};

struct TRIANGLE{
	struct VERTEX vertex[3];
};

struct SECTOR{
	int numtriangles;
	struct TRIANGLE *triangle;
};

struct SECTOR sector1;

GLubyte *loadBmp(const char *filename,unsigned int w,unsigned int h)
{
	unsigned char *buf;
	int fd = open(filename,O_RDONLY);
	if(fd < 0){
		printf("open file failed,errno:%s\n",strerror(errno));
		return NULL;
	}
	unsigned long buf_count = w * h * 3 * sizeof(unsigned char);
	buf = (unsigned char *)malloc(buf_count);
	if(!buf){
		printf("alloc memery failed,errno:%s\n",strerror(errno));
		close(fd);
		return NULL;
	}
	lseek(fd,54,SEEK_SET);
	if(read(fd,buf,buf_count) <= 0){
		printf("read file failed,errno:%s\n",strerror(errno));
		close(fd);
		free(buf);
		return NULL;
	}
	close(fd);
	return buf;
}

void read_one_line(FILE* f,char *buf)
{
	if((NULL == f) || (NULL == buf)){
		return;
	}
	do{
		fgets(buf,255,f);
	}while((buf[0] == '/') || (buf[0] == '\n') || (buf[0] == '\r'));
}

void setup_world()
{
	float x,y,z,u,v;
	int numtriangles;
	char buf[255];
	int i;
	FILE *file;

	file = fopen("3dworld.model","rt");
	read_one_line(file,buf);
	sscanf(buf,"NUMPOLLIES %d\n",&numtriangles);
	sector1.triangle = (struct TRIANGLE*)malloc(sizeof(struct TRIANGLE) * numtriangles);
	sector1.numtriangles = numtriangles;

	for(i = 0;i < numtriangles;i++){
		int vert;
		for(vert = 0;vert < 3;vert++){
			read_one_line(file,buf);
			sscanf(buf,"%f  %f  %f  %f  %f",&x,&y,&z,&u,&v);
			sector1.triangle[i].vertex[vert].x = x;
			sector1.triangle[i].vertex[vert].y = y;
			sector1.triangle[i].vertex[vert].z = z;
			sector1.triangle[i].vertex[vert].u = u;
			sector1.triangle[i].vertex[vert].v = v;
		}
	}
	fclose(file);
}

void init(void)
{
	GLubyte *image = loadBmp("bg.bmp",IMG_W,IMG_H);

	glGenTextures(3,&texture[0]);						// Create Three Textures
	// Create Nearest Filtered Texture
	glBindTexture(GL_TEXTURE_2D,texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D,0,3,IMG_W,IMG_H,0,GL_RGB,GL_UNSIGNED_BYTE,image);

	// Create Linear Filtered Texture
	glBindTexture(GL_TEXTURE_2D,texture[1]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D,0,3,IMG_W,IMG_H,0,GL_RGB,GL_UNSIGNED_BYTE,image);

	//Create MipMapped Texture
	glBindTexture(GL_TEXTURE_2D,texture[2]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
	glTexImage2D(GL_TEXTURE_2D,0,3,IMG_W,IMG_H,0,GL_RGB,GL_UNSIGNED_BYTE,image);
	gluBuild2DMipmaps(GL_TEXTURE_2D,3,IMG_W,IMG_H,GL_RGB,GL_UNSIGNED_BYTE,image);

	free(image);

	glEnable(GL_TEXTURE_2D);							// Enable Texture Mapping
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);					// Set The Blending Function For Translucency
	glClearColor(0.0f,0.0f,0.0f,0.0f);					// This Will Clear The Background Color To Black
	glClearDepth(1.0);									// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);								// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glShadeModel(GL_SMOOTH);							// Enables Smooth Color Shading
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

	setup_world();
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	GLfloat x_m,y_m,z_m,u_m,v_m;
	GLfloat xtrans = - xpos;
	GLfloat ztrans = - zpos;
	GLfloat ytrans = - walkbias - 0.25f;
	GLfloat sceneroty = 360.0f - yrot;

	glRotatef(lookupdown,1.0f,0,0);
	glRotatef(sceneroty,0,1.0f,0);

	glTranslatef(xtrans,ytrans,ztrans);
	glBindTexture(GL_TEXTURE_2D,texture[filter]);

	// Process Each Triangle
	int i;
	for(i = 0;i < sector1.numtriangles;i++){
		glBegin(GL_TRIANGLES);
			glNormal3f(0.0f,0.0f,1.0f);
			x_m = sector1.triangle[i].vertex[0].x;
			y_m = sector1.triangle[i].vertex[0].y;
			z_m = sector1.triangle[i].vertex[0].z;
			u_m = sector1.triangle[i].vertex[0].u;
			v_m = sector1.triangle[i].vertex[0].v;
			glTexCoord2f(u_m,v_m);
			glVertex3f(x_m,y_m,z_m);

			x_m = sector1.triangle[i].vertex[1].x;
			y_m = sector1.triangle[i].vertex[1].y;
			z_m = sector1.triangle[i].vertex[1].z;
			u_m = sector1.triangle[i].vertex[1].u;
			v_m = sector1.triangle[i].vertex[1].v;
			glTexCoord2f(u_m,v_m);
			glVertex3f(x_m,y_m,z_m);

			x_m = sector1.triangle[i].vertex[2].x;
			y_m = sector1.triangle[i].vertex[2].y;
			z_m = sector1.triangle[i].vertex[2].z;
			u_m = sector1.triangle[i].vertex[2].u;
			v_m = sector1.triangle[i].vertex[2].v;
			glTexCoord2f(u_m,v_m);
			glVertex3f(x_m,y_m,z_m);
		glEnd();
	}
	glFlush();
}

void reshape(int w, int h)
{
	glViewport(0,0,w,h);								// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)w / (GLfloat)h,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix
}

void keyboard (unsigned char key, int x, int y)
{
	switch (key){
		case 'w':
		case 'W':
			xpos -= (float)sin(heading * piover180) * 0.5f;
			zpos -= (float)cos(heading * piover180) * 0.5f;
			if(walkbiasangle >= 359.0f){
				walkbiasangle = 0.0f;
			} else{
				walkbiasangle += 10.0f;
			}
			break;
		case 's':
		case 'S':
			xpos += (float)sin(heading * piover180) * 0.5f;
			zpos += (float)cos(heading * piover180) * 0.5f;
			if(walkbiasangle <= 1.0f){
				walkbiasangle = 359.0f;
			} else{
				walkbiasangle -= 10.0f;
			}
			break;
		case 'a':
		case 'A':
			heading += 1.0f;
			yrot = heading;
			break;
		case 'd':
		case 'D':
			heading -= 1.0f;
			yrot = heading;
			break;
		case 'q':
		case 'Q':
			lookupdown -= 1.0f;
			break;
		case 'e':
		case 'E':
			lookupdown += 1.0f;
			break;
		case 'f':
		case 'F':
			filter += 1;
			if(filter >= 3){
				filter = 0;
			}
			break;
		case 'b':
		case 'B':
			blend = !blend;
			glEnable(blend ? GL_BLEND : GL_DEPTH_TEST);
			glDisable(blend ? GL_DEPTH_TEST : GL_BLEND);
			break;
		case 27:
			exit(0);
			break;
		default:
			return;
	}
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
	return 0;
}
